package net.sf.fc.script;

import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.xml.bind.JAXBException;

import org.xml.sax.SAXException;

import net.sf.fc.script.gen.options.Filter;
import net.sf.fc.script.gen.options.OptionsScript;
import net.sf.fc.script.gen.options.RenameOptions;
import net.sf.fc.script.gen.options.RenameSuffixOptions;
import net.sf.fc.script.gen.options.RenameType;
import net.sf.fc.script.gen.options.SrcDirOptions;

public class OptionsScriptProxy {

    private final OptionsScript options;
    private final ScriptHelper<OptionsScript> optionsHelper;
    private final File optionsXml;

    /**
     * Method that creates an OptionsScriptProxy object that represents the cached default OptionsScript
     * object saved in the classpath.
     * @param optionsHelper
     * @param defaultOptionsScript
     */
    public OptionsScriptProxy(ScriptHelper<OptionsScript> optionsHelper, OptionsScript defaultOptionsScript) {
        if(optionsHelper == null) {
            throw new IllegalArgumentException("optionsHelper must not be null.");
        }
        if(defaultOptionsScript == null) {
            throw new IllegalArgumentException("defaultOptionsScript must not be null.");
        }
        this.optionsHelper = optionsHelper;
        this.optionsXml = null;

        // Get a copy of the OptionsScript since OptionsScript is mutable.
        options = (OptionsScript)defaultOptionsScript.copyTo(null);
    }

    /**
     * Creates a OptionsScript object based on the contents of optionsXml if it exists. If the file does not
     * exist, a default OptionsScript object is created. When saveToDisk is called, optionsXml will be created
     * if it does not exist or overwritten if it is.
     * @param optionsHelper - ScriptHelper object that facilitates marshalling/unmarshalling of an OptionsScript
     * object.
     * @param optionsXml - File from which to unmarshal an OptionsScript object.
     * @throws JAXBException if unmarshal fails. It will fail if optionsXml is not an xml file that conforms to
     * the Options schema.
     * @throws SAXException
     * @throws IOException
     */
    public OptionsScriptProxy(ScriptHelper<OptionsScript> optionsHelper, File optionsXml)
            throws JAXBException, IOException, SAXException {
        if(optionsHelper == null) {
            throw new IllegalArgumentException("optionsHelper must not be null.");
        }
        if(optionsXml == null) {
            throw new IllegalArgumentException("optionsXml must not be null.");
        }

        this.optionsHelper = optionsHelper;
        this.optionsXml = optionsXml;

        //ensureOptionsXmlIsValid();
        // Make sure the options xml file contains a valid options xml script.
        optionsHelper.validate(optionsXml);

        options = optionsHelper.unmarshal(optionsXml);
        ensureFiltersNotNull(options);
    }

    /**
     * Static method for getting the default options values from /cfg/defaultOptions.xml. OptionsScriptProxy's constructors take
     * the default options script as an argument. This method should be used to get it. Passing it in instead of
     * OptionsScriptProxy generating it means it does not have to be unmarshalled every time an OptionsScriptProxy is instantiated.
     * @param optionsHelper
     * @return OptionsScript
     * @throws JAXBException
     */
    public static OptionsScript createDefaultOptionsObjectFromClasspath(ScriptHelper<OptionsScript> optionsHelper) throws JAXBException {
        URL url = OptionsScriptProxy.class.getResource("/cfg/defaultOptions.xml");
        OptionsScript optionsScript = url != null ? optionsHelper.unmarshal(url) : createDefaultOptionsObject();
        ensureFiltersNotNull(optionsScript);
        return optionsScript;
    }

    private static OptionsScript createDefaultOptionsObject() {
        RenameOptions ro = new RenameOptions();
        ro.setRenameSuffixOptions(new RenameSuffixOptions());

        SrcDirOptions sdo = new SrcDirOptions();

        OptionsScript options = new OptionsScript();
        options.setRenameOptions(ro);
        options.setSrcDirOptions(sdo);
        options.setCopyAttributes(true);
        options.setFollowLinks(true);

        return options;
    }

    private static void ensureFileCopyFilterNotNull(OptionsScript options) {
        if(options.getSrcDirOptions().getFileCopyFilter() == null) {
            options.getSrcDirOptions().setFileCopyFilter(new Filter());
            Filter filter = options.getSrcDirOptions().getFileCopyFilter();
            if(filter.getValue() == null) {
                filter.setValue("");
            }
        }
    }

    private static void ensureDirCopyFilterNotNull(OptionsScript options) {
        if(options.getSrcDirOptions().getDirCopyFilter() == null) {
            options.getSrcDirOptions().setDirCopyFilter(new Filter());
            Filter filter = options.getSrcDirOptions().getDirCopyFilter();
            if(filter.getValue() == null) {
                filter.setValue("");
            }
        }
    }

    private static void ensureFlattenFilterNotNull(OptionsScript options) {
        if(options.getSrcDirOptions().getFlattenFilter() == null) {
            options.getSrcDirOptions().setFlattenFilter(new Filter());
            Filter filter = options.getSrcDirOptions().getFlattenFilter();
            if(filter.getValue() == null) {
                filter.setValue("");
            }
        }
    }

    private static void ensureMergeFilterNotNull(OptionsScript options) {
        if(options.getSrcDirOptions().getMergeFilter() == null) {
            options.getSrcDirOptions().setMergeFilter(new Filter());
            Filter filter = options.getSrcDirOptions().getMergeFilter();
            if(filter.getValue() == null) {
                filter.setValue("");
            }
        }
    }

    private static void ensureFiltersNotNull(OptionsScript options) {
        ensureFileCopyFilterNotNull(options);
        ensureDirCopyFilterNotNull(options);
        ensureFlattenFilterNotNull(options);
        ensureMergeFilterNotNull(options);
    }

    // OptionsScript getter method. Return a copy of the OptionsScript object. To change the default settings, clients must
    // use the setter methods defined in this class.
    public boolean isCopyAttributes() {
        return options.isCopyAttributes();
    }

    public boolean isFollowLinks() {
        return options.isFollowLinks();
    }

    public boolean isBuildRestoreScript() {
        return options.getRenameOptions().isBuildRestoreScript();
    }

    public boolean isPromptBeforeOverwrite() {
        return options.getRenameOptions().isPromptBeforeOverwrite();
    }

    public boolean isUpdate() {
        return options.getRenameOptions().isUpdate();
    }

    public String getRenameSuffixFormat() {
        return options.getRenameOptions().getRenameSuffixOptions().getFormat();
    }

    public boolean isUseGMT() {
        return options.getRenameOptions().getRenameSuffixOptions().isUseGMT();
    }

    public RenameSuffixOptions getRenameSuffixOptions(RenameSuffixOptions rso) {
        return (RenameSuffixOptions)options.getRenameOptions().copyTo(null);
    }

    public RenameType getRenameType() {
        return options.getRenameOptions().getRenameType();
    }

    public RenameOptions getRenameOptions() {
        return (RenameOptions)options.getRenameOptions().copyTo(null);
    }

    public String getFileCopyFilterValue() {
        return options.getSrcDirOptions().getFileCopyFilter().getValue();
    }

    public boolean isFileCopyFilterExclusive() {
        return options.getSrcDirOptions().getFileCopyFilter().isExclusive();
    }

    public boolean isFileCopyFilterCaseSensitive() {
        return options.getSrcDirOptions().getFileCopyFilter().isCaseSensitive();
    }

    public boolean isFileCopyFilterRegEx() {
        return options.getSrcDirOptions().getFileCopyFilter().isRegularExpression();
    }

    public Filter getFileCopyFilter() {
        return (Filter)options.getSrcDirOptions().getFileCopyFilter().copyTo(null);
    }

    public String getDirCopyFilterValue() {
        return options.getSrcDirOptions().getDirCopyFilter().getValue();
    }

    public boolean isDirCopyFilterExclusive() {
        return options.getSrcDirOptions().getDirCopyFilter().isExclusive();
    }

    public boolean isDirCopyFilterCaseSensitive() {
        return options.getSrcDirOptions().getDirCopyFilter().isCaseSensitive();
    }

    public boolean isDirCopyFilterRegEx() {
        return options.getSrcDirOptions().getDirCopyFilter().isRegularExpression();
    }

    public Filter getDirCopyFilter() {
        return (Filter)options.getSrcDirOptions().getDirCopyFilter().copyTo(null);
    }

    public String getFlattenFilterValue() {
        return options.getSrcDirOptions().getFlattenFilter().getValue();
    }

    public boolean isFlattenFilterExclusive() {
        return options.getSrcDirOptions().getFlattenFilter().isExclusive();
    }

    public boolean isFlattenFilterCaseSensitive() {
        return options.getSrcDirOptions().getFlattenFilter().isCaseSensitive();
    }

    public boolean isFlattenFilterRegEx() {
        return options.getSrcDirOptions().getFlattenFilter().isRegularExpression();
    }

    public Filter getFlattenFilter() {
        return (Filter)options.getSrcDirOptions().getFlattenFilter().copyTo(null);
    }

    public String getMergeFilterValue() {
        return options.getSrcDirOptions().getMergeFilter().getValue();
    }

    public boolean isMergeFilterExclusive() {
        return options.getSrcDirOptions().getMergeFilter().isExclusive();
    }

    public boolean isMergeFilterCaseSensitive() {
        return options.getSrcDirOptions().getMergeFilter().isCaseSensitive();
    }

    public boolean isMergeFilterRegEx() {
        return options.getSrcDirOptions().getMergeFilter().isRegularExpression();
    }

    public Filter getMergeFilter() {
        return (Filter)options.getSrcDirOptions().getMergeFilter().copyTo(null);
    }

    public int getMaxCopyLevel() {
        return options.getSrcDirOptions().getMaxCopyLevel();
    }

    public int getMaxFlattenLevel() {
        return options.getSrcDirOptions().getMaxFlattenLevel();
    }

    public int getMaxMergeLevel() {
        return options.getSrcDirOptions().getMaxMergeLevel();
    }

    public SrcDirOptions setSrcDirOptions() {
        return (SrcDirOptions)options.getSrcDirOptions().copyTo(null);
    }

    /**
     * Gets a copy of the OptionsScript object that this OptionsScriptProxy represents. If this object represents the
     * default Options Script, the OptionsScript represents the one saved in the default options file.
     * @return
     */
    public OptionsScript getOptions() {
        return (OptionsScript)options.copyTo(null);
    }

    /**
     * Gets the default options script cached in this OptionsScriptProxy. If this object represents the default options
     * script, this method returns the default options from what is from the classpath. If this object represents a
     * saved Options script or a new Options script, this method returns the cached OptionsSript object that was saved
     * on disk when this OptionsSciptProxy object was created. If you want to know that you have the guaranteed latest
     * default options, call Request factory's getDefaultOptionsFromDisk.
     * @return
     */
//    public OptionsScript getDefaultOptions() {
//        return (OptionsScript)defaultOptions.copyTo(null);
//    }

    // OptionsScript setter methods
    public void setCopyAttributes(boolean preserve) {
        options.setCopyAttributes(preserve);
    }

    public void setFollowLinks(boolean follow) {
        options.setFollowLinks(follow);
    }

    public void setBuildRestoreScript(boolean build) {
        options.getRenameOptions().setBuildRestoreScript(build);
    }

    public void setPromptBeforeOverwrite(boolean prompt) {
        options.getRenameOptions().setPromptBeforeOverwrite(prompt);
    }

    public void setUpdate(boolean update) {
        options.getRenameOptions().setUpdate(update);
    }

    public void setRenameSuffixFormat(String renameSuffixFormat) {
        if(renameSuffixFormat == null) throw new NullPointerException("Rename suffix format string must not be null");
        options.getRenameOptions().getRenameSuffixOptions().setFormat(renameSuffixFormat);
    }

    public void setUseGMT(boolean useGMT) {
        options.getRenameOptions().getRenameSuffixOptions().setUseGMT(useGMT);
    }

    public void setRenameSuffixOptions(RenameSuffixOptions rso) {
        if(rso == null) throw new NullPointerException("RenameSuffixOptions must not be null");
        setRenameSuffixFormat(rso.getFormat());
        setUseGMT(rso.isUseGMT());
    }

    public void setRenameType(RenameType renameType) {
        if(renameType == null) throw new NullPointerException("Rename type must not be null");
        options.getRenameOptions().setRenameType(renameType);
    }

    public void setRenameOptions(RenameOptions renameOptions) {
        if(renameOptions == null) throw new NullPointerException("RenameOptions must not be null");
        setBuildRestoreScript(renameOptions.isBuildRestoreScript());
        setPromptBeforeOverwrite(renameOptions.isPromptBeforeOverwrite());
        setRenameSuffixOptions(renameOptions.getRenameSuffixOptions());
    }

    public void setFileCopyFilterValue(String value) {
        if(value == null) throw new NullPointerException("Copy filter value must not be null");
        options.getSrcDirOptions().getFileCopyFilter().setValue(value);
    }

    public void setFileCopyFilterExclusive(boolean exclusive) {
        options.getSrcDirOptions().getFileCopyFilter().setExclusive(exclusive);
    }

    public void setFileCopyFilterCaseSensitive(boolean caseSensitive) {
        options.getSrcDirOptions().getFileCopyFilter().setCaseSensitive(caseSensitive);
    }

    public void setFileCopyFilterRegEx(boolean regex) {
        options.getSrcDirOptions().getFileCopyFilter().setRegularExpression(regex);
    }

    public void setFileCopyFilter(Filter copyFilter) {
        // Filters are optional and can be null.
        if(copyFilter != null) {
            setFileCopyFilterValue(copyFilter.getValue());
            setFileCopyFilterExclusive(copyFilter.isExclusive());
            setFileCopyFilterCaseSensitive(copyFilter.isCaseSensitive());
            setFileCopyFilterRegEx(copyFilter.isRegularExpression());
        }
    }

    public void setDirCopyFilterValue(String value) {
        if(value == null) throw new NullPointerException("Copy filter value must not be null");
        options.getSrcDirOptions().getDirCopyFilter().setValue(value);
    }

    public void setDirCopyFilterExclusive(boolean exclusive) {
        options.getSrcDirOptions().getDirCopyFilter().setExclusive(exclusive);
    }

    public void setDirCopyFilterCaseSensitive(boolean caseSensitive) {
        options.getSrcDirOptions().getDirCopyFilter().setCaseSensitive(caseSensitive);
    }

    public void setDirCopyFilterRegEx(boolean regex) {
        options.getSrcDirOptions().getDirCopyFilter().setRegularExpression(regex);
    }

    public void setDirCopyFilter(Filter copyFilter) {
        // Filters are optional and can be null.
        if(copyFilter != null) {
            setDirCopyFilterValue(copyFilter.getValue());
            setDirCopyFilterExclusive(copyFilter.isExclusive());
            setDirCopyFilterCaseSensitive(copyFilter.isCaseSensitive());
            setDirCopyFilterRegEx(copyFilter.isRegularExpression());
        }
    }

    public void setFlattenFilterValue(String value) {
        if(value == null) throw new NullPointerException("Flatten filter value must not be null");
        options.getSrcDirOptions().getFlattenFilter().setValue(value);
    }

    public void setFlattenFilterExclusive(boolean exclusive) {
        options.getSrcDirOptions().getFlattenFilter().setExclusive(exclusive);
    }

    public void setFlattenFilterCaseSensitive(boolean caseSensitive) {
        options.getSrcDirOptions().getFlattenFilter().setCaseSensitive(caseSensitive);
    }

    public void setFlattenFilterRegEx(boolean regex) {
        options.getSrcDirOptions().getFlattenFilter().setRegularExpression(regex);
    }

    public void setFlattenFilter(Filter flattenFilter) {
        // Filters are optional and can be null.
        if(flattenFilter != null) {
            setFlattenFilterValue(flattenFilter.getValue());
            setFlattenFilterExclusive(flattenFilter.isExclusive());
            setFlattenFilterCaseSensitive(flattenFilter.isCaseSensitive());
            setFlattenFilterRegEx(flattenFilter.isRegularExpression());
        }
    }

    public void setMergeFilterValue(String value) {
        if(value == null) throw new NullPointerException("Merge filter value must not be null");
        options.getSrcDirOptions().getMergeFilter().setValue(value);
    }

    public void setMergeFilterExclusive(boolean exclusive) {
        options.getSrcDirOptions().getMergeFilter().setExclusive(exclusive);
    }

    public void setMergeFilterCaseSensitive(boolean caseSensitive) {
        options.getSrcDirOptions().getMergeFilter().setCaseSensitive(caseSensitive);
    }

    public void setMergeFilterRegEx(boolean regex) {
        options.getSrcDirOptions().getMergeFilter().setRegularExpression(regex);
    }

    public void setMergeFilter(Filter mergeFilter) {
        // Filters are optional and can be null.
        if(mergeFilter != null) {
            setMergeFilterValue(mergeFilter.getValue());
            setMergeFilterExclusive(mergeFilter.isExclusive());
            setMergeFilterCaseSensitive(mergeFilter.isCaseSensitive());
            setMergeFilterRegEx(mergeFilter.isRegularExpression());
        }
    }

    public void setMaxCopyLevel(int maxCopyLevel) {
        if(maxCopyLevel < 0) throw new IllegalArgumentException("Maximum copy level must be greater than or equal to 0");
        options.getSrcDirOptions().setMaxCopyLevel(maxCopyLevel);
    }

    public void setMaxFlattenLevel(int maxFlattenLevel) {
        if(maxFlattenLevel < -1) throw new IllegalArgumentException("Maximum flatten level must be greater than or equal to -1");
        options.getSrcDirOptions().setMaxFlattenLevel(maxFlattenLevel);
    }

    public void setMaxMergeLevel(int maxMergeLevel) {
        if(maxMergeLevel < -1) throw new IllegalArgumentException("Maximum merge level must be greater than or equal to -1");
        options.getSrcDirOptions().setMaxMergeLevel(maxMergeLevel);
    }

    public void setSrcDirOptions(SrcDirOptions sdo) {
        if(sdo == null) throw new NullPointerException("SrcDirOptions must not be null");
        setFileCopyFilter(sdo.getFileCopyFilter());
        setFlattenFilter(sdo.getFlattenFilter());
        setMergeFilter(sdo.getMergeFilter());
        setMaxCopyLevel(sdo.getMaxCopyLevel());
        setMaxFlattenLevel(sdo.getMaxFlattenLevel());
        setMaxMergeLevel(sdo.getMaxMergeLevel());
    }

    public void setOptions(OptionsScript options) {
        if(options == null) throw new NullPointerException("OptionsScript must not be null");
        setCopyAttributes(options.isCopyAttributes());
        setRenameOptions(options.getRenameOptions());
        setSrcDirOptions(options.getSrcDirOptions());
    }

    public void restoreDefaultOptions() {
        setOptions(createDefaultOptionsObject());
    }

    public void saveOptionsToDisk() throws JAXBException {
        if(optionsXml == null) {
            throw new IllegalStateException("The Options object has not been saved to disk. Try calling saveOptionsToDiskAs(File optionsXml) instead.");
        }
        OptionsScript optionsFromDisk = optionsXml.exists() ? optionsHelper.unmarshal(optionsXml) : null;
        if(!options.equals(optionsFromDisk)) {
            optionsHelper.marshal(optionsXml, options);
        }
    }

    public OptionsScriptProxy saveOptionsToDiskAs(File optionsXml) throws JAXBException, IOException, SAXException {
        if(optionsXml == null) throw new NullPointerException("Options file must not be null");
        OptionsScript optionsFromDisk = optionsXml.exists() ? optionsHelper.unmarshal(optionsXml) : null;
        if(!options.equals(optionsFromDisk)) {
            optionsHelper.marshal(optionsXml, options);
        }
        return new OptionsScriptProxy(optionsHelper, optionsXml);
    }

    public File getFile() {
        return optionsXml;
    }

    /**
     * Returns true if optionsXml is not null and the file it represents exists.
     * @return boolean
     */
    public boolean isOnDisk() {
        return optionsXml != null && optionsXml.exists();
    }

    /**
     * Returns true if optionsXml is not null. Clients can call this before calling saveToDisk to avoid
     * an IllegalStateException
     * @return boolean
     */
    public boolean isFileConfigured() {
        return optionsXml != null;
    }
}
